/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/28 15:40
  *
  * @file       pd_wwdg.c
  * @brief      Xxx hardware driver.
  *
  * @note       This file contains the hardware driver for the Xxx.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/28     Chris        the first version
  *
  ******************************************************************************
  */


#include <stm32f4xx_ll_bus.h>
#include <stm32f4xx_ll_wwdg.h>
#include <stm32f4xx_ll_rcc.h>
#include "pd_wwdg.h"


static uint32_t WinWDG_pow(uint32_t Y) {
    uint32_t Result = 1;
    while (Y--) Result *= 2;
    return Result;
}

static double WinWDG_getValue(uint32_t timeout, uint32_t prescaler) {
    uint32_t pclk1 = 0;
    if      (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_1)  pclk1 = 168000000 / 1;
    else if (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_2)  pclk1 = 168000000 / 2;
    else if (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_4)  pclk1 = 168000000 / 4;
    else if (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_8)  pclk1 = 168000000 / 8;
    else if (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_16) pclk1 = 168000000 / 16;
    else {}

    return 1.0 * timeout / 1000 * pclk1 / 4096 / WinWDG_pow(prescaler >> 7);
}

/**
 * watchdog timeout: t = 1/PCLK1 * 4096 * 2^WDGTB[1:0] * (T[5:0] +1)
 * windown time: t = 1/PCLK1 * 4096 * 2^WDGTB[1:0] * (T[5:0] - W[5:0])
 *
 * @param super
 */
static void WinWDG_init_(const WDG *super) {
    const WinWDG *this = (const WinWDG *) super;

    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_WWDG);
    LL_WWDG_SetPrescaler(this->WWDGx, this->Prescaler);
    LL_WWDG_SetWindow(this->WWDGx, this->Window);
    LL_WWDG_SetCounter(this->WWDGx, this->Counter);
    LL_WWDG_Enable(this->WWDGx);
}

static void WinWDG_feedDogs_(const WDG *super) {
    const WinWDG *this = (const WinWDG *) super;
    LL_WWDG_SetCounter(this->WWDGx, this->Counter);
}

static uint32_t WinWDG_isReset_(const WDG *super) {
    return LL_RCC_IsActiveFlag_WWDGRST() == SET ? 1 : 0;
}

void WinWDG_constructor(WinWDG *this, WWDG_TypeDef *WWDGx, uint32_t Prescaler, uint32_t max, uint32_t min) {
    static struct WDGVtbl vtbl = {
            .init      = &WinWDG_init_,
            .feed_dogs = &WinWDG_feedDogs_,
            .is_reset  = &WinWDG_isReset_,
    };
    WDG_constructor(&this->wdg);

    this->wdg.vptr = &vtbl;

    double timeout = WinWDG_getValue(max, Prescaler) - 1;
    double window = timeout - WinWDG_getValue(min, Prescaler);

    this->WWDGx     = WWDGx;
    this->Prescaler = Prescaler;
    this->Window    = 0x40 | (uint8_t) window;
    this->Counter   = 0x40 | (uint8_t) timeout;
    this->min       = min;
    this->max       = max;

}

